<?php
namespace app\api\controller\wap;


use app\command\UdpServer;
use app\common\model\coin\CallApi;
use app\common\model\coin\CoinRuleRun;
use app\common\model\coin\CoinRuleRunTips;
use app\common\model\coin\CoinRuleRunTrade;
use app\common\model\system\SqlToRedis;
use think\Exception;
use think\facade\Db;

class Trade extends Base{

    /**
     * 错误信息
     */
    const ERROR_TYPE_LIST = [
        'version'   => 'REFRESH',
        'system'    => 'SYSTEM',
        'monitor'    => 'MONITOR',   // 无监控权限
    ];


    /**
     * 统一抛出异常调用
     * @param $message
     * @throws \Exception
     */
    private function throwErr($message) {
        try {
            // 刷新用户规则
            (new SqlToRedis())->getUserRule(1,true);
        }catch (\Exception $e) {
        }
        throw new \Exception($message);
    }

    /**
     * 判断是否允许操作
     * @param array $userDetails
     * @return bool|void
     * @throws \Exception
     */
    public function checkMonitorLimit(array $userDetails) {
        // 判断是否允许监控
        if(!\app\common\model\users\Users::isMonitor($userDetails)) {
            return $this->throwErr(static::ERROR_TYPE_LIST['monitor']);
        }
        return true;
    }


    /**
     * @param array $params 必须参数 coin_rule_id、coin_rule_run_id
     */
    public function firstOrder(array $params) {
        if($params['coin_rule_run_id'] == 0) {  // 不存在执行规则，先创建规则
            Db::startTrans();
            try {

                /************** 获取上次执行结果数据 ******************/
                if(!$lastRunDetails = (new \app\common\model\coin\CoinRule())->recordAll([
                    'id'    => $params['coin_rule_id'],
                ],1,[],['last_run.*'])) {
                    return $this->throwErr(static::ERROR_TYPE_LIST['system']);
                }
                $lastDetails = [];
                foreach($lastRunDetails as $key=>$val) {
                    $key = explode('last_run-',$key);
                    if(count($key) == 2) {
                        $lastDetails[$key[1]] = $val;
                    }
                }
                /************ END 获取上次执行结果数据 ****************/

                $runDetails = (new CoinRuleRun())->recordCreateDefault($params['coin_rule_id'],$lastDetails);
                $params['coin_rule_run_id'] = $runDetails['id'];
                Db::commit();
                (new SqlToRedis())->getUserRule(1,true);
            }catch (\Exception $e){
                Db::rollback();
                return $this->throwErr(static::ERROR_TYPE_LIST['system']);
            }
        }

        // 获取并验证详情，错误返回false
        if(!$details = $this->fetchDetails($params['coin_rule_id'],$params['coin_rule_run_id'])) {
            return $this->throwErr(static::ERROR_TYPE_LIST['system']);
        }

        // 判断监控权限
        $this->checkMonitorLimit($details['user']);


        // 添加计算数量参数
     //   $lastFields = ['last_run-id'=>'last_id','last_run-quantity','last_run-sell_quantity','last_run-last_remaining_quantity','last_run-deviations_quantity','last_run-reserved_quantity'];
        $lastFields = ['last_run-reserved_price' => 'reserved_price','last_run-reserved_quantity'=>'reserved_quantity'];
        foreach($lastFields as $field=>$val) {
            $field = is_string($field)?$field:$val;
            $details['params'][$val] = $details[$field]?$details[$field]:0;
        }
        /************** 添加申请 ****************/

        Db::startTrans();
        try {
            $insertData = (new CoinRuleRunTrade())->firsterOrder($params['coin_rule_run_id'],$details['params']);
            Db::commit();
        }catch (\Exception $e){
            Db::rollback();
            return $this->throwErr(static::ERROR_TYPE_LIST['system']);
        }
        /************ END 添加申请 **************/

        /********** 调用第三方接口，并修改交易订单状态 ***********/
        if(!is_array($errorType = (new CallApi())->placeOrder($insertData,$details['params'],$details['params']['symbol'],true))) {
            if($tipsMsg = CoinRuleRunTips::getTradeErrTypeToTipType($errorType,$insertData['type'])) {
                /******** 添加错误提示 ***********/
                (new CoinRuleRunTips())->insertTips(array_merge($insertData,$tipsMsg),$tipsMsg['tipsType']);
                /****** END 添加错误提示 *********/
            }
            return $this->throwErr($errorType);
        }
        /******** END 调用第三方接口，并修改交易订单状态 *********/

        /******** 添加提示信息 ***********/
        // 计算上期保留金额
        $insertData['reserved_amount'] = ceil(bcmul($insertData['reserved_quantity'],$insertData['reserved_price'],20));
        (new CoinRuleRunTips())->insertTips($insertData);
        /****** END 添加提示信息 *********/

        (new SqlToRedis())->getUserRule(1,true);
        (new SqlToRedis())->getTradeMonitor(true);
        return $insertData['id'];
    }


    /**
     * 补仓操作
     * @param array $params 参数 [coin_rule_id、coin_rule_run_id、unit_price]
     * @param bool $manualAmount 手动加仓金额
     * @param int $version  coin_rule_run 版本号（版本号不一致返回false）
     */
    public function coverOrder(array $params,$manualAmount = null,$version = null) {
        // 获取并验证详情，错误返回false
        if(!($details = $this->fetchDetails($params['coin_rule_id'],$params['coin_rule_run_id'])) || (!$manualAmount && $details['coin_rule_run-cover_times_auto'] != $params['cover_times'])) {
            return $this->throwErr(static::ERROR_TYPE_LIST['system']);
        }

        // 判断监控权限
        $this->checkMonitorLimit($details['user']);

        if($manualAmount) {
            if($manualAmount < 0 || $version != $details['params']['version']) {
                return $this->throwErr(static::ERROR_TYPE_LIST['version']);
            }
            $details['params'] = array_merge($details['params'],[
                'first_amount'  => $manualAmount,
                'cover_times'   => -1,
            ]);
        }

        /************** 添加申请 ****************/
        Db::startTrans();
        try {
            $insertData = (new CoinRuleRunTrade())->coverOrder($params['coin_rule_run_id'],$details['params']);
            Db::commit();
        }catch (\Exception $e){
            Db::rollback();
            return $this->throwErr(static::ERROR_TYPE_LIST['system']);
        }
        /************ END 添加申请 **************/

        /********** 调用第三方接口，并修改交易订单状态 ***********/
        if(!is_array($errorType = (new CallApi())->placeOrder($insertData,$details['params'],$details['params']['symbol'],true))) {
            if($tipsMsg = CoinRuleRunTips::getTradeErrTypeToTipType($errorType,$insertData['type'])) {
                /******** 添加错误提示 ***********/
                (new CoinRuleRunTips())->insertTips(array_merge($insertData,$tipsMsg),$tipsMsg['tipsType']);
                /****** END 添加错误提示 *********/
            }
            return $this->throwErr($errorType);
        }
        /******** END 调用第三方接口，并修改交易订单状态 *********/

        /******** 添加提示信息 ***********/
        // 添加最低价提示
        if(!$manualAmount) {
            (new CoinRuleRunTips())->insertTips(array_merge($insertData,['price_min'=>$params['price_min']]),CoinRuleRunTips::TYPE_LIST['PRICE_MIN']);
        }

        // 加仓类型提示
        (new CoinRuleRunTips())->insertTips($insertData);
        /****** END 添加提示信息 *********/

        (new SqlToRedis())->getUserRule(1,true);
        (new SqlToRedis())->getTradeMonitor(true);
        return $insertData['id'];
    }


    /**
     * 止盈操作
     * @param array $params 参数 必须字段：[coin_rule_id、coin_rule_run_id、unit_price]
     * @param bool $price 手动清仓的单价，不填则为自动清仓
     */
    public function clearanceOrder(array $params,$price = null) {
        // 获取并验证详情，错误返回false
        if(!$details = $this->fetchDetails($params['coin_rule_id'],$params['coin_rule_run_id'])) {
            return $this->throwErr(static::ERROR_TYPE_LIST['system']);
        }

        // 判断监控权限
        $this->checkMonitorLimit($details['user']);

        if($price) {
            $details['params']['cover_times'] = -1;
            $params['price_current'] = $price;
        } else {
            if($params['price_current'] <=0) {
                return $this->throwErr(static::ERROR_TYPE_LIST['system']);
            }
            $details['params']['cover_times'] = 0;
        }

        // 设置其他参数
        $details['params'] = array_merge($details['params'],[
           'quantity'   => $details['coin_rule_run-quantity'],
           'deduct_fees'   => $details['coin_rule_run-deduct_fees'],    // 抵扣的手续费数量
           'last_remaining_quantity'   => $details['coin_rule_run-last_remaining_quantity'],    // 上次卖出剩余数量
           'deviations_quantity'   => $details['coin_rule_run-deviations_quantity'],    // 买入偏差量
           'deviations_calculation'   => $details['deviations_calculation'],    // 是否需要偏差量计算
           'unit_price' => $params['price_current'],
        ]);


        /************** 添加申请 ****************/
        Db::startTrans();
        try {
            if(!$insertData = (new CoinRuleRunTrade())->clearanceOrder($params['coin_rule_run_id'],$details['params'])) {
                throw new Exception('run error');
            }
            Db::commit();
        }catch (\Exception $e){
            Db::rollback();
            return $this->throwErr(static::ERROR_TYPE_LIST['system']);
        }
        /************ END 添加申请 **************/

        /********** 调用第三方接口，并修改交易订单状态 ***********/
        try {
            Db::startTrans();
            if(!$errorType = (new CallApi())->placeOrder($insertData,$details['params'],$details['params']['symbol'],true)) {
                return $this->throwErr(static::ERROR_TYPE_LIST['system']);
            }
            Db::commit();
        }catch (\Exception $e) {
            Db::rollback();
            return $this->throwErr(static::ERROR_TYPE_LIST['system']);
        }
        // 请求第三方
        if(!is_array($errorType)) {
            if($tipsMsg = CoinRuleRunTips::getTradeErrTypeToTipType($errorType,$insertData['type'])) {
                /******** 添加错误提示 ***********/
                (new CoinRuleRunTips())->insertTips(array_merge($insertData,$tipsMsg),$tipsMsg['tipsType']);
                /****** END 添加错误提示 *********/
            }
            try {
                // 请求第三方错误时计算偏差量处理
                UdpServer::request(UdpServer::CMD_LIST['DEVIATIONS_CORRECT'],null,[
                    'runId' => $params['coin_rule_run_id'],
                ]);
            } catch (\Exception $e) {
            }
            return $this->throwErr($errorType);
        }
        /******** END 调用第三方接口，并修改交易订单状态 *********/

        /******** 添加提示信息 ***********/
        // 添加最高价提示
        if(!$price) {
            (new CoinRuleRunTips())->insertTips(array_merge($insertData,['price_max'=>$params['price_max']]),CoinRuleRunTips::TYPE_LIST['PRICE_MAX']);
        }

        // 清仓类型提示
         (new CoinRuleRunTips())->insertTips($insertData);
        /****** END 添加提示信息 *********/

        (new SqlToRedis())->getUserRule(1,true);
        (new SqlToRedis())->getTradeMonitor(true);
        return $insertData['id'];
    }


    /**
     * 监控订单状态
     * @param $value 订单数据
     */
    public function monitorStatus($value) {
        $callApiClass = new CallApi();
        $coinRuleRunTradeClass = new CoinRuleRunTrade();

        $isUpdate = false;
        if(bccomp($value['amount'],0,20) !=0 || bccomp($value['quantity'],0,20) !=0 ) {
            if(!$value['order_id']) {   // 订单ID不存在，获取订单ID
                $orderDetails = $callApiClass->getClientOrder($value['client_order_id'],$value,$isUpdate);
            } else {
                $orderDetails = $callApiClass->getOrder($value['order_id'],$value,$isUpdate);
            }
        } else {
            // 获取默认
            $orderDetails = $callApiClass->getDefaultOrder();
        }

        if($orderDetails && ($updateData = $callApiClass->compTradeUpdateData($value,$orderDetails))) { // 判断是否正确获取到数据【存在则进行修改订单处理】
            $isUpdate = true;
            $isNotice = false;
            Db::startTrans();
            try {

                // 更新订单
                if($coinRuleRunTradeClass->orderUpdate($value['id'],$updateData,$value['version'])) {
                    if(array_key_exists('status',$updateData)) {
                        $isNotice = true;
                    }
                }
                Db::commit();
            }catch (\Exception $e){
                Db::rollback();
            }

            if($isNotice && ($tradeDetails = $coinRuleRunTradeClass->recordAll(['id'=>$value['id']],1))) { // 判断并发送消息通知
                /******** 添加提示信息 ***********/
                (new CoinRuleRunTips())->insertTips($tradeDetails);
                /****** END 添加提示信息 *********/
            }
        }
        if($isUpdate) {
            (new SqlToRedis())->getTradeMonitor(true);
        }

    }


    /**
     * 获取规则详情及执行抓过你太
     * @param $ruleId  规则ID
     * @param $ruleRunId 执行中ID
     * @return array|bool  错误 OR 详情
     */
    private function fetchDetails($ruleId,$ruleRunId) {
        // 获取详情
        if(!($details = (new \app\common\model\coin\CoinRule())->recordAll([
                'id'    => $ruleId,
           //     'status'    => CoinRuleRun::STATUS_OPEN,
            ],1,[],['coin_rule_run.*','users_key.*','coin.*','last_run.*',
                'user-vip','user-vip_expire','user-has_service_charge','user-local_balance' // 添加用户状态
            ])) || $details['coin_rule_run-id'] != $ruleRunId) {
            return false;
        }
        $details['user'] = [
            'vip'   => $details['user-vip'],
            'vip_expire'   => $details['user-vip_expire'],
            'has_service_charge'   => $details['user-has_service_charge'],
            'local_balance'   => $details['user-local_balance'],
        ];

        $details['params'] = [
            'first_amount'  => $details['first_amount'],
            'user_id'  => $details['user_id'],
            'coin_rule_id'  => $details['id'],
            'coin_id'  => $details['coin_id'],
            'coin_name'  => $details['coin-name'],
            'cover_times'  => $details['coin_rule_run-cover_times_auto'] + 1,
            'isDouble'  => \app\common\model\coin\CoinRule::isFirstDouble($details['is_first_double']),
            'version'  => $details['coin_rule_run-version'],  //当前版本号

            'trade_rule'  => $details['trade_rule'],  //交易规则
            'sell_type'  => $details['sell_type'],  //没出类型【限价或市价】
            'is_open'  => $details['is_open'],  //启动状态
            'type'  => $details['type'],  //策略类型


            'symbol'    => $details['coin-code'],
            'account_id'    => $details['users_key-account_id'],
            'access_key'    => $details['users_key-access_key'],
            'secret_key'    => $details['users_key-secret_key'],
            'sell_price_scale'  => $details['coin-sell_price_scale'],
            'sell_number_scale'  => $details['coin-sell_number_scale'],
            'sell_number_min'  => $details['coin-sell_number_min'],
            'sell_total_min'  => $details['coin-sell_total_min'],
            'buy_total_min'  => $details['coin-buy_total_min'],
            'buy_money_scale'   => 8,
        ];

        return $details;
    }

}